package fileHandling;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashMap;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import com.jme.util.CloneImportExport;

import fileHandling.language.properties.SettingsProps;

import logic.nodes.nodeSettings.AbstractSettings;
import logic.nodes.nodeSettings.Settings;
import logic.nodes.nodeSettings.upgrades.UpgradableSettings;
import logic.nodes.nodeSettings.upgrades.UpgradeValue;

/**
 * Provides static methods to load and save {@link AbstractSettings}.
 * 
 * @author Wasserleiche
 */
public class SettingsLoader {
		
	public static HashMap<String, AbstractSettings<?>> loadedSettings = new HashMap<String, AbstractSettings<?>>();
	
	/**
	 * Returns the loaded {@link AbstractSettings} of the given texture-path. This method shall only be called if 
	 * there is a {@link AbstractSettings} stored in the {@link HashMap}!.
	 * @param settingsPath The path of the {@link AbstractSettings} to be retrieved.
	 * @return The {@link AbstractSettings}.
	 */
	public static AbstractSettings<?> getLoadedSettings(String settingsPath) {
		assert(settingsPath != null);
		AbstractSettings<?> savedSettings = loadedSettings.get(settingsPath);
		AbstractSettings<?> copy = null;
		if(savedSettings instanceof Settings) copy = new Settings((Settings)savedSettings);
		else copy = new UpgradableSettings((UpgradableSettings)savedSettings);
		return copy;
	}
	
	/**
	 * Creates a new {@link CloneImportExport}-object out of the given {@link AbstractSettings} and stores it in the 
	 * loadedObjects-{@link HashMap} with the path.
	 * @param modelPath The path that will become the key in the {@link HashMap}.
	 * @param model The {@link AbstractSettings} that has to be saved in a {@link CloneImportExport}-object.
	 */
	protected static void putLoadedSettings(String settingsPath, AbstractSettings<?> settings) {
		assert(settingsPath != null && settings != null);
		loadedSettings.put(settingsPath, settings);
	}
	
	public static boolean objectIsLoaded(String settingsFile) {
		return loadedSettings.containsKey(settingsFile);
	}
	
	/**
	 * Creates a new {@link AbstractSettings}-Object by reading the given file.
	 * @param settingsFile The file to be read.
	 * @return A new {@link AbstractSettings}-Object. It contains either all information from the file or 
	 * an error-message, if an error occurred.
	 */
	public static Settings loadSettings(String settingsFile) {
		if(objectIsLoaded(settingsFile)) return (Settings)getLoadedSettings(settingsFile);
		Settings settings = new Settings();
		
		Document doc = getDocument(settingsFile);
		if(doc == null) return null;
		
		Element root = doc.getRootElement();
		for(Object child : root.getChildren()) {
			Element element = (Element)child;
			if(element == null) continue;
			settings.addOption(element.getName(), element.getAttributeValue(SettingsProps.value.toString()));
		}
		
		putLoadedSettings(settingsFile, settings);
		
		return settings;
	}
	
	public static Document getDocument(String settingsFile) {
		SAXBuilder builder = new SAXBuilder();
		try {
			Document doc = builder.build(new File(settingsFile));
			return doc;
		} catch(Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static UpgradableSettings loadUpgradableSettings(String settingsFile) {
		Document doc = getDocument(settingsFile);
		if(doc == null) return null;
		
		Element root = doc.getRootElement();
		int costs = Integer.valueOf(root.getAttributeValue(SettingsProps.costs.toString())).intValue();
		String modelPath = root.getAttributeValue(SettingsProps.modelPath.toString());
		UpgradableSettings settings = new UpgradableSettings(costs, modelPath, settingsFile);
		for(Object child : root.getChildren()) {
			Element element = (Element)child;
			if(element == null) continue;
			HashMap<String, UpgradeValue> values = new HashMap<String, UpgradeValue>();
			for(Object elemChild : element.getChildren()) {
				Element elem = (Element)elemChild;
				if(elem == null) continue;
				int upgradeCosts = 0;
				String level = "0";
				String name = elem.getName();
				if(!name.equals(SettingsProps.Default.toString())) {
					upgradeCosts = Integer.valueOf(elem.getAttributeValue(SettingsProps.upgradeCosts.toString())).intValue();
					level = name.substring(SettingsProps.level.toString().length());
				}
				String value = elem.getAttributeValue(SettingsProps.value.toString());
				UpgradeValue upgradeValue = new UpgradeValue(upgradeCosts, value);
				values.put(level, upgradeValue);
			}
			settings.addOption(element.getName(), values);
		}
		
		putLoadedSettings(settingsFile, settings);
		
		return settings;
	}
	
	/**
	 * Writes all information of the given {@link AbstractSettings}-Object into the given file. If there is any error 
	 * while writing the file, the {@link AbstractSettings}-Object will contain an error-message. 
	 * @param settingsFile The file to write the information to.
	 * @param settings The {@link AbstractSettings}-Object whose information shall be written.
	 */
	public static void saveSettings(String settingsFile, Settings settings) {
		int lastSlash = settingsFile.lastIndexOf("/");
		String name = settingsFile.substring(lastSlash+1);
		Document doc = new Document(new Element(name.replaceAll(".xml", "")));
		Element root = doc.getRootElement();
		root.setAttribute(SettingsProps.type.toString(), SettingsProps.Default.toString()); 
		for(String key : settings.getKeys()) {
			Element elem = new Element(key);
			elem.setAttribute(SettingsProps.value.toString(), settings.getValueOf(key));
			root.addContent(elem);
		}
		
		OutputStream out = null;
		try {
			File file = new File(settingsFile);
			if(!file.exists()) file.createNewFile();
			out = new FileOutputStream(file);
			new XMLOutputter(Format.getPrettyFormat()).output(doc, out);
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(out != null) {
				try { out.close(); }
				catch(Exception e) { e.printStackTrace(); }
			}
		}
	}
}